function acq_list = note_rdscm(note_file);

% NOTE_READ - parse notefiles for stimulus protocols
%
%    LIST = NOTE_READ(FILE) parses the DATAC note file FILE to obtain
%    the stimulus protocols used to generate the data in a corresponding
%    DATAC file.  The output is a structure array LIST of length N,
%    where N is the number of acquisitions initiated.

% By:   S.C. Molitor (smolitor@med.unc.edu)
% Date: January 12, 2000

% initialize outputs/variables to prevent MATLAB errors

acq_list = [];
record_num = [];
clock_time = '';

% validate arguments

if (nargin ~= 1)
   msgbox('Invalid number of arguments', 'NOTE_READ Error', 'warn');
   return
elseif (~ischar(note_file) | isempty(note_file))
   msgbox('FILE must be a string', 'NOTE_READ Error', 'warn');
   return
end

% open file
% exit if not found

fid = fopen(note_file, 'rt');
if (fid == -1)
   msgbox(sprintf('File %s not found', note_file), 'NOTE_READ Error', 'warn');
   return
else
   line = fgetl(fid);
   if ((line == -1) | (feof(fid)))
      msgbox(sprintf('File %s is empty', note_file), 'NOTE_READ Error', 'warn');
      return
   end
   line_cnt = 1;
end

% check for first record number
% can only parse newer version note files

while (~isempty(line))
   
   % strip token from input line
   % obtain token type
   
   [token, line, line_cnt] = next_token(line, line_cnt, fid);
   if (isempty(token))
      msgbox('Cannot find first record', 'NOTE_READ Error', 'warn');
      fclose(fid);
      return
   else
      token_type = get_token_type(token);
   end
   
   % obtain record number if token is appropriate type
   % obtain time string if token is appropriate type
   % exit loop when first record number is found
   
   if (token_type == 'R')
      record_num = sscanf(token, '[R:%d');
      break
   elseif (token_type == 'T')
      clock_time = token;
   end
end

% check for invalid first record number

if (isempty(record_num))
   msgbox(sprintf('Invalid record number found (line %d)', line_cnt), 'NOTE_READ Error', 'warn');
   fclose(fid);
elseif (record_num ~= 1)
   msgbox(sprintf('Cannot parse pre-1994 note files (line %d)', line_cnt), 'NOTE_READ Error', 'warn');
   fclose(fid);
   return
end

% initialize acquisition counter
% counter stores number of acquisitions queued to be written (0, 1 or 2)
% previous acquisition stored in PAR(1) & subsequent ones in PAR(2)
% parameters written when ACQ_STATE = 2
% sequence goes input -> PAR(2) -> PAR(1) -> output

acq_state = 0;

% initialize data parameter structure
% maintain two copies for storing parameters on next acquisition
% when previous one hasn't been flushed (ACQ_STATE = 2)

field_name = {'time', 'valve', 'note', 'stim_file', 'beg_rec', 'end_rec'};
default_value = {clock_time, 1, {}, '', record_num, record_num};
data_par = defstruc([], field_name, default_value);
data_par(2) = data_par(1);

% initialize acquisition parameters structure
% maintain two copies for storing parameters on next acquisition
% when previous one hasn't been flushed (ACQ_STATE = 2)

field_name = {'pri_chan', 'pri_seq', 'sec_chan', 'sec_seq', 'seq_cnt', 'acq_mode', 'ave_cnt', 'take_cnt'};
default_value = {0, '', 0, '', 0, '', 0, 0};
acq_par = defstruc([], field_name, default_value);
acq_par(2) = acq_par(1);

% proceed through file in tokenwise fashion
% exit when EOF reached

while (~isempty(line))
   
   % strip token from input line
   % check for empty token or remainder
   % obtain token type
   
   [token, line, line_cnt] = next_token(line, line_cnt, fid);
   if (isempty(token))
      break
   else
      token_type = get_token_type(token);
   end
   
   % execute if token is command, record or time
   % ignore other tokens
   
   switch (token_type)
      
   case 'C'		% token is a command string
      
      % execute command
      
      command_str = token(2 : end);
      switch (command_str)
         
      case {'g', 'get'}		% get - get a new stimulus file
         
         % obtain stimulus file name
         % overwrite previous stimulus file name if no acquisition
         % overwrite next stimulus file name regardless
         
         [stim_file, line, line_cnt] = get_stim_file(line, line_cnt, fid);
         if (isempty(stim_file))
            break
         else
            if (acq_state == 0)
               data_par(1).stim_file = stim_file;
            end
            data_par(2).stim_file = stim_file;
         end
         
      case 'seq'				% seq - execute an acquisition sequence
         
         % update acquisition counter if valid sequence
         % update next acquisition parameters (input -> PAR(2))
         
         [acq_struct, line, line_cnt] = get_acq_par(line, line_cnt, fid);
         if (isstruct(acq_struct))
            acq_state = acq_state + 1;
            acq_par(acq_state) = acq_struct;
            
            % update time for next acquisition
            % update first record for next acquisition
            % update last record for previous acquisition
            
            [token, line, line_cnt] = find_token_type('T', line, line_cnt, fid);
            if (get_token_type(token) == 'T')
               data_par(acq_state).time = token;
            end
            [token, line, line_cnt] = find_token_type('R', line, line_cnt, fid);
            if (get_token_type(token) == 'R')
               data_par(2).beg_rec = sscanf(token, '[R:%d');
               data_par(1).end_rec = data_par(2).beg_rec - 1;
            end
         end
            
      case 'do'				% do - execute sequence stored in a stimulus file
         
         % obtain stimulus file name
         % overwrite previous stimulus file name if no acquisition
         % overwrite next stimulus file name regardless
         
         [stim_file, line, line_cnt] = get_stim_file(line, line_cnt, fid);
         if (isempty(stim_file))
            break
         else
            if (acq_state == 0)
               data_par(1).stim_file = stim_file;
            end
            data_par(2).stim_file = stim_file;
         end
         
         % update acquisition counter if valid sequence
         % update next acquisition parameters (input -> PAR(2))
         
         [acq_struct, line, line_cnt] = get_acq_par(line, line_cnt, fid);
         if (isstruct(acq_struct))
            acq_state = acq_state + 1;
            acq_par(acq_state) = acq_struct;
            
            % update time for next acquisition
            % update first record for next acquisition
            % update last record for previous acquisition
            
            [token, line, line_cnt] = find_token_type('T', line, line_cnt, fid);
            if (get_token_type(token) == 'T')
               data_par(acq_state).time = token;
            end
            [token, line, line_cnt] = find_token_type('R', line, line_cnt, fid);
            if (get_token_type(token) == 'R')
               data_par(2).beg_rec = sscanf(token, '[R:%d');
               data_par(1).end_rec = data_par(2).beg_rec - 1;
            end
         end
         
      case {'take', 'exttake'}	% take/exttake - take specified number of records
         
         % obtain number of records being taken
         
         [token, line, line_cnt] = find_token('TAKING', line, line_cnt, fid);
         if (strcmp(token, 'TAKING'))
            [token, line, line_cnt] = next_token(line, line_cnt, fid);
            
            % update acquisition counter if number of records found
            % reset next acquisition parameters (input -> PAR(2))
            
            if (~isempty(token))
               acq_state = acq_state + 1;
               acq_par(acq_state) = reset_acq_par(acq_par(acq_state), command_str, 1, sscanf(token, '%d'));
            end
         end
         
      case {'ave', 'average'}		% average - average specified number of acquisitions
         
         % obtain number of acquisitions being averaged
         
         [token, line, line_cnt] = find_token('AVERAGING', line, line_cnt, fid);
         if (strcmp(token, 'AVERAGING'))
            [token, line, line_cnt] = next_token(line, line_cnt, fid);
            
            % update acquisition counter if number of acquisitions found
            % reset next acquisition parameters (input -> PAR(2))
            
            if (~isempty(token))
               acq_state = acq_state + 1;
               acq_par(acq_state) = reset_acq_par(acq_par(acq_state), 'average', sscanf(token, '%d'), 1);
            end
         end
         
      case 'until'			% until - take records until timer value reached
         
         % obtain seconds on timer before acquisition is stopped
         
         [token, line, line_cnt] = find_token('Until', line, line_cnt, fid);
         if (strcmp(token, 'Until'))
            [token, line, line_cnt] = next_token(line, line_cnt, fid);
            
            % update acquisition counter if timer limit found
            % reset next acquisition parameters (input -> PAR(2))
            % NOTE: store timer limit in TAKE_CNT field of ACQ_PAR
            %       too lazy to create separate field for timer limit
            
            if (~isempty(token))
               acq_state = acq_state + 1;
               acq_par(acq_state) = reset_acq_par(acq_par(acq_state), 'until', 1, sscanf(token, '%d'));
            end
         end
         
      case {'da', 'data'}			% data - take records until stopped
         
         % update acquisition counter
            % reset next acquisition parameters (input -> PAR(2))
         
         acq_state = acq_state + 1;
         acq_par(acq_state) = reset_acq_par(acq_par(acq_state), 'data', 1, 0);
         
      case {'v0', 'v1', 'v2', 'v3', 'v4'}		% vX - change the valve to X
         
         % overwrite previous valve setting if no acquisition
         % overwrite next valve setting regardless
         
         data_par(2).valve = sscanf(command_str, 'v%d');
         if (acq_state == 0)
            data_par(1).valve = data_par(2).valve;
         end
         
      case 'note'				% note - add text comment
         
         % add tokens to note until time string is found
         % add time string back to line so it gets picked up!!
         
         note_line = '';
         while (~isempty(line))
            old_cnt = line_cnt;
            [token, line, line_cnt] = next_token(line, line_cnt, fid);
            if (isempty(token))
               break
            elseif (get_token_type(token) == 'T')
               line = [token ' ' line];
               break
            elseif (isempty(note_line))
               note_line = token;
            else
               note_line = [note_line ' ' token];
            end
            
            % store line & reset if line count incremented
            
            if (old_cnt ~= line_cnt)
               data_par(acq_state + 1).note{end + 1} = note_line;
               note_line = '';
            end
         end
         
         % add last line if it is not empty
         
         if (~isempty(note_line))
            data_par(acq_state + 1).note{end + 1} = note_line;
         end
        
      case {'ac', 'acl', 'aclose'}		% aclose - close acquisition file
         break
         
      otherwise				% ignore other commands
      end
      
      % store parameters after second acquisition has been found (PAR(1) -> output)
      % shift acquisition & sequence information (PAR(2) -> PAR(1))
      % reset notes & acquisition counter
      
      if (acq_state == 2)
         acq_list = store_par(acq_list, data_par(1), acq_par(1));
         data_par(1) = data_par(2);
         acq_par(1) = acq_par(2);
         data_par(2).note = {};
         acq_state = 1;
      end
      
   case 'R'		% token is a record number
      
      % update first record for next acquisition
      % update last record for previous acquisition
      
      data_par(2).beg_rec = sscanf(token, '[R:%d');
      data_par(1).end_rec = data_par(2).beg_rec - 1;
      
   case 'T'		% token is a time string
      
      % update time for next acquisition
      % update time for previous acquisition only if none stored
      
      data_par(2).time = token;
      if (acq_state == 0)
         data_par(1).time = token;
      end
      
   otherwise
   end
   
end

% store remaining acquisition information
% add any remaining notes first

if (acq_state > 0)
   data_par(1).note = {data_par(1).note{:} data_par(2).note{:}};
   acq_list = store_par(acq_list, data_par(1), acq_par(1));
end

% close file and return

fclose(fid);
return



% NEXT_TOKEN - get the next token in the file

function [token, new_line, new_cnt] = next_token(old_line, old_cnt, fid);

% initialize outputs

token = '';
new_line = old_line;
new_cnt = old_cnt;

% obtain next token in line
% obtain next line if token or remainder is empty
% check for EOF when obtaining next line

while (isempty(token))
   [token, new_line] = strtok(new_line, ' ');
   while (isempty(deblank(new_line)))
      new_line = fgetl(fid);
      if (feof(fid) | (new_line == -1))
         return
      else
         new_cnt = new_cnt + 1;
      end
   end
end
return



% FIND_TOKEN - forward search for a specified token

function [token, new_line, new_cnt] = find_token(pattern, old_line, old_cnt, fid);

% initialize outputs

token = '';
new_line = old_line;
new_cnt = old_cnt;

% loop through tokens until PATTERN is found
% exit if EOF or empty token or line returned

while (~strcmp(token, pattern) & ~isempty(new_line))
   [token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
   if (isempty(token))
      break
   end
   
   % do housekeeping for various tokens found here
   
   switch (get_token_type(token))
      
   case 'C'		% command string
      
      % if command is found, pattern must have been interrupted
      % add command back to line for processing & return nothing
      
      new_line = [token ' ' new_line];
      token = '';
      break
      
   case 'R'		% record string
      
      % should update record number here
      % have no pressing reason to do so at the moment
      
   case 'T'		% time string
      
      % should update time string here
      % non-sequence acquisition commands (take, da, etc)
      % may return slightly incorrect times
      % but I'm not that concerned
      
   otherwise	% unknown type - do nothing
   end
end
return



% FIND_TOKEN_TYPE - forward search for a specified token type

function [token, new_line, new_cnt] = find_token_type(token_type, old_line, old_cnt, fid);

% initialize output

token = '';
new_line = old_line;
new_cnt = old_cnt;

% loop through tokens until TYPE is found
% exit if EOF or empty token or line returned

while ((get_token_type(token) ~= token_type) & ~isempty(new_line))
   [token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
   if (isempty(token))
      break
   end
end
return



% GET_TOKEN_TYPE - find the type of token

function token_type = get_token_type(token);

% initialize output

token_type = 'U';

% check if token is a command ('>XXX')

if (length(token) >= 2)
   if (token(1) == '>')
      token_type = 'C';
   end
end

% check if token is a record ('[R:XXX'])

if (length(token) >= 4)
   if (strcmp(token(1 : 3), '[R:') & all(~isletter(token(4 : end - 1))))
      token_type = 'R';
   end
end

% check if token is a time string ('XX:XX:XX')

if (length(token) == 8)
   if (all(~isletter(token([1 2 4 5 7 8]))) & (token(3) == ':') & (token(6) == ':'))
      token_type = 'T';
   end
end
return



% GET_STIM_FILE - obtains the stimulus file name

function [stim_file, new_line, new_cnt] = get_stim_file(old_line, old_cnt, fid);

% initialize outputs

stim_file = '';
new_line = old_line;
new_cnt = old_cnt;

% obtain stimulus file name
% should appear immediately after 'STM:'
% strip path & extension from stimulus filename

[token, new_line, new_cnt] = find_token('STM:', new_line, new_cnt, fid);
if (strcmp(token, 'STM:') & ~isempty(new_line))
   [token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
   if (~isempty(token))
      [path, stim_file, ext, vers] = fileparts(token);
   end
end
return



% GET_ACQ_PAR - obtain acquisition parameters from stimulus sequence

function [acq_par, new_line, new_cnt] = get_acq_par(old_line, old_cnt, fid);

% initialize outputs

acq_par = [];
new_line = old_line;
new_cnt = old_cnt;

% determine sequence type
% old ('O'): 'SEQ ch: <chan> <seq> <mode> <# acq>'
% primary ('P'):  'PRI SEQ ch: <chan> seq: <seq> ACQ MODE: <mode> <# acq>'
% secondary ('S'):  'PRI SEQ ch: <chan> seq: <seq> SEC SEQ ch: <chan> seq: <seq> ACQ MODE: <mode> <# acq>'
% return if not unknown ('U')

index = findstr(new_line, 'SEC SEQ');
if (isempty(index))
   index = findstr(new_line, 'PRI SEQ');
   if (isempty(index))
      index = findstr(new_line, 'SEQ');
      if (isempty(index))
         return
      else
         seq_type = 'O';
      end
   else
      seq_type = 'P';
   end
else
   seq_type = 'S';
end

% obtain channel number
% skip 'ch:' token

[token, new_line, new_cnt] = find_token('ch:', new_line, new_cnt, fid);
if (strcmp(token, 'ch:'))
   [token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
   acq_par.pri_chan = sscanf(token, '%d');
else
   acq_par = [];
   return
end

% obtain sequence string
% skip 'seq:' token for type P/S

if ((seq_type == 'P') | (seq_type == 'S'))
   [token, new_line, new_cnt] = find_token('seq:', new_line, new_cnt, fid);
   if (~strcmp(token, 'seq:'))
      acq_par = [];
      return
   end
end
[acq_par.pri_seq, new_line, new_cnt] = next_token(new_line, new_cnt, fid);

% obtain secondary channel & sequence for type S
% otherwise clear these values

if (seq_type == 'S')
   [token, new_line, new_cnt] = find_token('ch:', new_line, new_cnt, fid);
   if (strcmp(token, 'ch:'))
      [token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
      acq_par.sec_chan = sscanf(token, '%d');
   else
      acq_par = [];
      return
   end
   [token, new_line, new_cnt] = find_token('seq:', new_line, new_cnt, fid);
   if (strcmp(token, 'seq:'))
      [acq_par.sec_seq, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
   else
      acq_par = [];
      return
   end
else
   acq_par.sec_chan = [];
   acq_par.sec_seq = '';
end

% calculate expected length of sequence
% include PRI & SEC sequences in calculations

acq_par.seq_cnt = length(seqparse(acq_par.pri_seq));
if (~isempty(acq_par.sec_seq))
   acq_par.seq_cnt = acq_par.seq_cnt * length(seqparse(acq_par.sec_seq));
end

% obtain acquisition mode
% obtain acquisition mode parameter
% skip 'MODE:' token for type P/S

if ((seq_type == 'P') | (seq_type == 'S'))
   [token, new_line, new_cnt] = find_token('MODE:', new_line, new_cnt, fid);
   if (~strcmp(token, 'MODE:'))
      acq_par = [];
      return
   end
end
[acq_par.acq_mode, new_line, new_cnt] = next_token(new_line, new_cnt, fid);
[token, new_line, new_cnt] = next_token(new_line, new_cnt, fid);

% store acquisition mode parameter in appropriate location
% parameter location depends on acquisition mode ('data' or 'average')

if (strcmp(acq_par.acq_mode, 'data'))
   acq_par.ave_cnt = 1;
   acq_par.take_cnt = sscanf(token, '%d');
elseif (strcmp(acq_par.acq_mode, 'average'))
   acq_par.ave_cnt = sscanf(token, '%d');
   acq_par.take_cnt = 1;
else
   acq_par.ave_cnt = 1;
   acq_par.take_cnt = sscanf(token, '%d');
end
return



% RESET_ACQ_PAR - reset values of acquisition parameters structure

function new_par = reset_acq_par(old_par, acq_mode, ave_cnt, take_cnt);

% initialize output

new_par = old_par;

% clear sequence information

new_par.pri_chan = 0;
new_par.pri_seq = '';
new_par.sec_chan = 0;
new_par.sec_seq = '';
new_par.seq_cnt = 0;

% update acquisition mode
% update average count
% update take count

new_par.acq_mode = acq_mode;
new_par.ave_cnt = ave_cnt; 
new_par.take_cnt = take_cnt;
return



% STORE_PAR - copy acquisition parameters to output structure

function new_list = store_par(old_list, data_par, acq_par);

% initialize output & variables

new_list = old_list;
note_list = {};

% obtain length of output structure
% add entry if records advanced
% return if ending record decremented (no records acquired)
% overwrite previous if beginning record did not advance (after no records acquired)
% save notes from previous acquisition before overwriting

list_pos = length(new_list);
if (list_pos == 0)
   list_pos = 1;
elseif (data_par(1).beg_rec > new_list(list_pos).beg_rec)
   list_pos = list_pos + 1;
elseif (data_par(1).end_rec < new_list(list_pos).end_rec)
   return
else
   note_list = new_list(list_pos).note;
end

% copy acquisition information to output

new_list(list_pos).time = lower(data_par.time);
new_list(list_pos).valve = data_par.valve;
new_list(list_pos).note = {note_list{:} data_par.note{:}};
new_list(list_pos).stim_file = lower(data_par.stim_file);
new_list(list_pos).beg_rec = data_par.beg_rec;
new_list(list_pos).end_rec = data_par.end_rec;

% copy sequence information to output

new_list(list_pos).pri_chan = acq_par.pri_chan;
new_list(list_pos).pri_seq = lower(acq_par.pri_seq);
new_list(list_pos).sec_chan = acq_par.sec_chan;
new_list(list_pos).sec_seq = lower(acq_par.sec_seq);
new_list(list_pos).seq_cnt = acq_par.seq_cnt;
new_list(list_pos).acq_mode = lower(acq_par.acq_mode);
new_list(list_pos).ave_cnt = acq_par.ave_cnt;
new_list(list_pos).take_cnt = acq_par.take_cnt;
return
